/* Copyright (c) 2022 - 2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "fff.h"
#include "gtest/gtest.h"

#include "cmsis_os2.h"
#include "nimble/nimble_npl.h"

class TestNimbleNplOs : public testing::Test {
public:
    TestNimbleNplOs()
    {
        // simple fake (return_val)
        RESET_FAKE(osMessageQueueNew);
        RESET_FAKE(osMessageQueueGetCount);
        RESET_FAKE(osMutexNew);
        RESET_FAKE(osMutexAcquire);
        RESET_FAKE(osMutexRelease);
        RESET_FAKE(osSemaphoreAcquire);
        RESET_FAKE(osSemaphoreRelease);
        RESET_FAKE(osTimerStart);
        RESET_FAKE(osKernelGetTickCount);
        RESET_FAKE(osDelay);

        // complex fake
        RESET_FAKE(osMessageQueuePut);
        RESET_FAKE(osMessageQueueGet);
        RESET_FAKE(osTimerStop);
        RESET_FAKE(osTimerNew);
    }
};

/*
 * Event queue
 */

TEST_F(TestNimbleNplOs, initializing_event_queue_with_a_null_pointer_aborts_execution)
{
    EXPECT_EXIT(ble_npl_eventq_init(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, initializing_event_queue_aborts_if_a_new_os_queue_could_not_be_created)
{
    osMessageQueueNew_fake.return_val = NULL; // = failure

    struct ble_npl_eventq event_queue;

    EXPECT_EXIT(ble_npl_eventq_init(&event_queue), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, retrieving_an_event_from_a_nullptr_event_queue_triggers_aborts_execution)
{
    EXPECT_EXIT(ble_npl_eventq_get(nullptr, 0), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, retrieving_an_event_from_an_event_queue_returns_non_nullptr_when_an_event_is_available)
{
    osMessageQueueGet_fake.custom_fake = [](osMessageQueueId_t q, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout) {
        *static_cast<void **>(msg_ptr) = reinterpret_cast<void *>(0xDEADBEEF);
        return osOK;
    };

    struct ble_npl_eventq event_queue;

    EXPECT_EQ(reinterpret_cast<struct ble_npl_event *>(0xDEADBEEF),
              static_cast<void *>(ble_npl_eventq_get(&event_queue, 0)));
}

TEST_F(TestNimbleNplOs, retrieving_an_event_from_an_event_queue_returns_nullptr_when_no_event_is_available)
{
    osMessageQueueGet_fake.return_val = osErrorResource;

    ble_npl_eventq event_queue;

    EXPECT_EQ(nullptr, ble_npl_eventq_get(&event_queue, 0));
}

TEST_F(TestNimbleNplOs, getting_event_queue_handle_cmsis_error_like_an_empty_queue)
{
    osMessageQueueGet_fake.return_val = osError;

    ble_npl_eventq event_queue;

    EXPECT_EQ(nullptr, ble_npl_eventq_get(&event_queue, 0));
}

TEST_F(TestNimbleNplOs, appending_an_event_to_a_nullptr_event_queue_aborts_execution)
{
    struct ble_npl_event event;
    EXPECT_EXIT(ble_npl_eventq_put(nullptr, &event), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, appending_a_nullptr_event_to_an_event_queue_aborts_execution)
{
    ble_npl_eventq event_queue;
    EXPECT_EXIT(ble_npl_eventq_put(&event_queue, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, appending_an_event_to_an_event_queue_aborts_execution_when_the_event_could_not_be_appended)
{
    osMessageQueuePut_fake.return_val = osErrorTimeout;

    ble_npl_eventq event_queue;
    struct ble_npl_event event;
    EXPECT_EXIT(ble_npl_eventq_put(&event_queue, &event), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, removing_an_event_from_a_nullptr_event_queue_aborts_execution)
{
    struct ble_npl_event event;
    EXPECT_EXIT(ble_npl_eventq_remove(nullptr, &event), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, removing_a_nullptr_event_from_an_event_queue_aborts_execution)
{
    struct ble_npl_eventq event_queue;
    EXPECT_EXIT(ble_npl_eventq_remove(&event_queue, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, testing_the_emptyness_of_a_nullptr_event_queue_aborts_execution)
{
    EXPECT_EXIT(ble_npl_eventq_is_empty(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, non_empty_event_queue_is_reported_as_non_empty)
{
    osMessageQueueGetCount_fake.return_val = 1;
    struct ble_npl_eventq event_queue;
    EXPECT_FALSE(ble_npl_eventq_is_empty(&event_queue));
}

TEST_F(TestNimbleNplOs, empty_event_queue_is_reported_as_empty)
{
    osMessageQueueGetCount_fake.return_val = 0;
    struct ble_npl_eventq event_queue;
    EXPECT_TRUE(ble_npl_eventq_is_empty(&event_queue));
}

/*
 * Event
 */

TEST_F(TestNimbleNplOs, initializing_a_nullptr_event_aborts_execution)
{
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    EXPECT_EXIT(ble_npl_event_init(nullptr, mock_callback, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, initializing_an_event_sets_its_callback)
{
    struct ble_npl_event event = {0};
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    ble_npl_event_init(&event, mock_callback, nullptr);
    EXPECT_EQ(event.callback, mock_callback);
}

TEST_F(TestNimbleNplOs, initializing_an_event_sets_its_context)
{
    struct ble_npl_event event = {0};
    void *context = reinterpret_cast<void *>(0xDEADBEEF);
    ble_npl_event_init(&event, nullptr, context);
    EXPECT_EQ(ble_npl_event_get_arg(&event), context);
}

TEST_F(TestNimbleNplOs, checking_queued_status_of_a_null_pointer_event_aborts_execution)
{
    EXPECT_EXIT(ble_npl_event_is_queued(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, initialised_event_is_reported_as_not_queued)
{
    struct ble_npl_event event;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);

    ble_npl_event_init(&event, mock_callback, nullptr);

    EXPECT_FALSE(ble_npl_event_is_queued(&event));
}

TEST_F(TestNimbleNplOs, queued_event_is_reported_as_queued)
{
    struct ble_npl_eventq event_queue;
    struct ble_npl_event event;

    ble_npl_eventq_put(&event_queue, &event);

    EXPECT_TRUE(ble_npl_event_is_queued(&event));
}

TEST_F(TestNimbleNplOs, getting_the_argument_of_a_null_pointer_event_aborts_execution)
{
    EXPECT_EXIT(ble_npl_event_get_arg(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, setting_the_argument_of_a_null_pointer_event_aborts_execution)
{
    EXPECT_EXIT(ble_npl_event_set_arg(nullptr, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, running_a_null_pointer_event_aborts_execution)
{
    EXPECT_EXIT(ble_npl_event_run(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

// test that the function pointer is tested against nullprt before calling it
TEST_F(TestNimbleNplOs, running_a_valid_event_with_a_null_function_aborts_execution)
{
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_event event;
    ble_npl_event_init(&event, mock_callback, nullptr);
    event.callback = nullptr;
    EXPECT_EXIT(ble_npl_event_run(&event), ::testing::KilledBySignal(SIGABRT), "");
}

/*
 * Mutex
 */

TEST_F(TestNimbleNplOs, initializing_a_null_pointer_mutex_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_mutex_init(nullptr));
}

TEST_F(TestNimbleNplOs, initializing_a_mutex_with_cmsis_error_returns_an_error)
{
    struct ble_npl_mutex mutex;
    osMutexNew_fake.return_val = nullptr;
    EXPECT_EQ(BLE_NPL_ERROR, ble_npl_mutex_init(&mutex));
}

TEST_F(TestNimbleNplOs, taking_a_null_pointer_mutex_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_mutex_pend(nullptr, 0));
}

TEST_F(TestNimbleNplOs, taking_a_mutex_with_cmsis_error_returns_an_error)
{
    struct ble_npl_mutex mutex;
    ble_npl_mutex_init(&mutex);
    osMutexAcquire_fake.return_val = osError;
    EXPECT_EQ(BLE_NPL_ERROR, ble_npl_mutex_pend(&mutex, 0));
}

TEST_F(TestNimbleNplOs, taking_a_mutex_handle_timing_out_as_a_specific_error)
{
    struct ble_npl_mutex mutex;
    ble_npl_mutex_init(&mutex);
    osMutexAcquire_fake.return_val = osErrorTimeout;
    EXPECT_EQ(BLE_NPL_TIMEOUT, ble_npl_mutex_pend(&mutex, 1));
}

TEST_F(TestNimbleNplOs, releasing_a_null_pointer_mutex_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_mutex_release(nullptr));
}

TEST_F(TestNimbleNplOs, releasing_a_mutex_with_cmsis_error_returns_an_error)
{
    struct ble_npl_mutex mutex;
    ble_npl_mutex_init(&mutex);
    osMutexRelease_fake.return_val = osError;
    EXPECT_EQ(BLE_NPL_ERROR, ble_npl_mutex_release(&mutex));
}

/*
 * Semaphore
 */

TEST_F(TestNimbleNplOs, initializing_a_null_pointer_semaphore_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_sem_init(nullptr, 16));
}

TEST_F(TestNimbleNplOs, taking_a_null_pointer_semaphore_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_sem_pend(nullptr, 0));
}

TEST_F(TestNimbleNplOs, taking_a_semaphore_with_cmsis_error_returns_an_error)
{
    struct ble_npl_sem semaphore;
    ble_npl_sem_init(&semaphore, 16);
    osSemaphoreAcquire_fake.return_val = osError;
    EXPECT_EQ(BLE_NPL_ERROR, ble_npl_sem_pend(&semaphore, 0));
}

TEST_F(TestNimbleNplOs, taking_a_semaphore_handle_timing_out_as_a_specific_error)
{
    struct ble_npl_sem semaphore;
    ble_npl_sem_init(&semaphore, 16);
    osSemaphoreAcquire_fake.return_val = osErrorTimeout;
    EXPECT_EQ(BLE_NPL_TIMEOUT, ble_npl_sem_pend(&semaphore, 1));
}

TEST_F(TestNimbleNplOs, releasing_a_null_pointer_semaphore_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_sem_release(nullptr));
}

TEST_F(TestNimbleNplOs, releasing_a_semaphore_with_cmsis_error_returns_an_error)
{
    struct ble_npl_sem semaphore;
    ble_npl_sem_init(&semaphore, 16);
    osSemaphoreRelease_fake.return_val = osError;
    EXPECT_EQ(BLE_NPL_ERROR, ble_npl_sem_release(&semaphore));
}

TEST_F(TestNimbleNplOs, getting_a_null_pointer_semaphore_count_aborts_execution)
{
    EXPECT_EXIT(ble_npl_sem_get_count(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

/*
 * Callout
 */

TEST_F(TestNimbleNplOs, initializing_a_null_pointer_callout_aborts_execution)
{
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    EXPECT_EXIT(
        ble_npl_callout_init(nullptr, &event_queue, mock_callback, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, initializing_a_callout_with_a_null_pointer_event_queue_aborts_execution)
{
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_callout callout;
    EXPECT_EXIT(
        ble_npl_callout_init(&callout, nullptr, mock_callback, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, initializing_a_callout_with_a_null_pointer_function_aborts_execution)
{
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    EXPECT_EXIT(ble_npl_callout_init(&callout, &event_queue, nullptr, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, initializing_a_callout_with_osTimerNew_cmsis_error_aborts_execution)
{
    osTimerNew_fake.return_val = nullptr;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    EXPECT_EXIT(
        ble_npl_callout_init(&callout, &event_queue, mock_callback, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, resetting_a_null_pointer_callout_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_callout_reset(nullptr, 1));
}

TEST_F(TestNimbleNplOs, resetting_a_callout_with_a_0_tick_timer_returns_an_error)
{
    struct ble_npl_callout callout;
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_callout_reset(&callout, 0));
}

TEST_F(TestNimbleNplOs, resetting_a_callout_changes_the_expiry)
{
    osTimerStart_fake.return_val = osOK;

    uint32_t current_tick = 10;
    osKernelGetTickCount_fake.return_val = current_tick;

    struct ble_npl_callout callout = {0};
    const ble_npl_time_t ticks = 5;
    EXPECT_EQ(BLE_NPL_OK, ble_npl_callout_reset(&callout, ticks));
    EXPECT_EQ(current_tick + ticks, ble_npl_callout_get_ticks(&callout));
}

TEST_F(TestNimbleNplOs, resetting_a_callout_returns_an_error_if_the_callout_could_not_be_reset)
{
    struct ble_npl_callout callout = {0};
    osTimerStart_fake.return_val = osError;
    EXPECT_NE(BLE_NPL_OK, ble_npl_callout_reset(&callout, 1));
}

TEST_F(TestNimbleNplOs, stopping_a_null_pointer_callout_aborts_execution)
{
    EXPECT_EXIT(ble_npl_callout_stop(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, stopping_an_unitialized_callout_dont_trigger_a_cmsis_error)
{
    struct ble_npl_callout callout;
    callout.id = nullptr;
    ble_npl_callout_stop(&callout);
    ASSERT_EQ(osTimerStop_fake.call_count, 0);
}

TEST_F(TestNimbleNplOs, stopping_a_callout_with_cmsis_error_aborts_execution)
{
    osTimerNew_fake.return_val = reinterpret_cast<void *>(1234546789);
    osTimerStart_fake.return_val = osOK;
    osKernelGetTickCount_fake.return_val = 0;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    ble_npl_callout_init(&callout, &event_queue, mock_callback, nullptr);

    osTimerStop_fake.return_val = osErrorParameter;
    EXPECT_EXIT(ble_npl_callout_stop(&callout), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, checking_a_null_pointer_callout_activity_aborts_execution)
{
    EXPECT_EXIT(ble_npl_callout_is_active(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, getting_a_null_pointer_callout_next_tick_aborts_execution)
{
    EXPECT_EXIT(ble_npl_callout_get_ticks(nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, getting_a_callout_next_tick_is_not_affected_by_timer_counter_overflow)
{
    osTimerNew_fake.return_val = reinterpret_cast<void *>(1234546789);
    osTimerStart_fake.return_val = osOK;
    osKernelGetTickCount_fake.return_val = UINT32_MAX - 5;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    ble_npl_callout_init(&callout, &event_queue, mock_callback, nullptr);
    // get the function that's called at each timer fire
    void (*on_timer_fire)(void *) = osTimerNew_fake.arg0_val;

    // set a period of 10 ticks
    ble_npl_callout_reset(&callout, 10);

    // advance time
    osKernelGetTickCount_fake.return_val = UINT32_MAX - 4;
    // the next timeout should now be at tick #4
    uint32_t timeout = ble_npl_callout_get_ticks(&callout);
    EXPECT_EQ(4, timeout);

    // advance time
    osKernelGetTickCount_fake.return_val = 4;
    on_timer_fire(&callout);
    // the next timeout should now be at tick #14
    timeout = ble_npl_callout_get_ticks(&callout);
    EXPECT_EQ(14, timeout);
}

// test that the next timeout isn't on the same tick as a callout reset (it must wait at least one period)
TEST_F(TestNimbleNplOs, getting_a_callout_next_tick_dont_return_starting_tick)
{
    osTimerNew_fake.return_val = reinterpret_cast<void *>(1234546789);
    osTimerStart_fake.return_val = osOK;
    osKernelGetTickCount_fake.return_val = 111;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    ble_npl_callout_init(&callout, &event_queue, mock_callback, nullptr);
    // set a period of 10 ticks
    ble_npl_callout_reset(&callout, 10);

    // we're still at 111. We just test if the next timeout is different than right now.
    uint32_t timeout = ble_npl_callout_get_ticks(&callout);
    EXPECT_NE(111, timeout);
}

// test that, if the timer overflow and we land back on the exact start timestamp,
// this timestamp will be a valid return. This means it does not trigger the
// ignore_start exclusion from the previous test.
TEST_F(TestNimbleNplOs, getting_a_callout_next_tick_can_return_tha_starting_tick_if_the_timer_overflow)
{
    osTimerNew_fake.return_val = reinterpret_cast<void *>(1234546789);
    osTimerStart_fake.return_val = osOK;
    uint32_t time_counter = 111;
    osKernelGetTickCount_fake.return_val = time_counter;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    ble_npl_callout_init(&callout, &event_queue, mock_callback, nullptr);
    // get the function that's called at each timer fire
    void (*on_timer_fire)(void *) = osTimerNew_fake.arg0_val;
    // set a period of UINT32_MAX / 4 ticks
    ble_npl_callout_reset(&callout, 1 << 30);

    // and now we fast forward the time
    time_counter += 1 << 30;
    osKernelGetTickCount_fake.return_val = time_counter;
    on_timer_fire(&callout);
    time_counter += 1 << 30;
    osKernelGetTickCount_fake.return_val = time_counter;
    on_timer_fire(&callout);
    time_counter += 1 << 30;
    osKernelGetTickCount_fake.return_val = time_counter;
    on_timer_fire(&callout);
    time_counter += 1 << 30;
    osKernelGetTickCount_fake.return_val = time_counter;
    // we should be back to 111 with a legal overflow
    EXPECT_EQ(111, time_counter);

    // the callout haven't been run yet, so the next timeout should be right
    // now, at the same timestamp as the start of the timer
    uint32_t timeout = ble_npl_callout_get_ticks(&callout);
    EXPECT_EQ(111, timeout);
}

// test that, if a timeout is bound to happen this tick, we do get the correct
// timestamps for the next timeout depending if we call ble_npl_callout_get_ticks
// before or after this tick's timeout.
TEST_F(TestNimbleNplOs, getting_a_callout_next_tick_check_if_the_timer_has_already_fired_this_tick)
{
    osTimerNew_fake.return_val = reinterpret_cast<void *>(1234546789);
    osTimerStart_fake.return_val = osOK;

    osKernelGetTickCount_fake.return_val = 111;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    ble_npl_callout_init(&callout, &event_queue, mock_callback, nullptr);
    // get the function that's called at each timer fire
    void (*on_timer_fire)(void *) = osTimerNew_fake.arg0_val;
    // set a period of 10 ticks
    ble_npl_callout_reset(&callout, 10);
    osKernelGetTickCount_fake.return_val = 121;

    // the callout haven't been called yet, so the next timeout should be right now
    uint32_t timeout = ble_npl_callout_get_ticks(&callout);
    EXPECT_EQ(121, timeout);

    on_timer_fire(&callout);

    // but now it have been called so although the current tick is the same, the next
    // timeout should be later
    timeout = ble_npl_callout_get_ticks(&callout);
    EXPECT_EQ(131, timeout);
}

TEST_F(TestNimbleNplOs, getting_a_null_pointer_callout_remaining_ticks_aborts_execution)
{
    EXPECT_EXIT(ble_npl_callout_remaining_ticks(nullptr, 0), ::testing::KilledBySignal(SIGABRT), "");
}

TEST_F(TestNimbleNplOs, getting_a_callout_remaining_ticks_is_not_affected_by_timer_counter_overflow)
{
    osTimerNew_fake.return_val = reinterpret_cast<void *>(1234546789);
    osTimerStart_fake.return_val = osOK;
    uint32_t time_counter = UINT32_MAX - 5;
    osKernelGetTickCount_fake.return_val = time_counter;
    ble_npl_event_fn *mock_callback = reinterpret_cast<ble_npl_event_fn *>(0xDEADBEEF);
    struct ble_npl_eventq event_queue;
    struct ble_npl_callout callout;
    ble_npl_callout_init(&callout, &event_queue, mock_callback, nullptr);
    // set a period of 10 ticks
    ble_npl_callout_reset(&callout, 10);

    uint32_t remaining = ble_npl_callout_remaining_ticks(&callout, time_counter);
    EXPECT_EQ(10, remaining);
}

TEST_F(TestNimbleNplOs, setting_a_null_pointer_callout_argument_aborts_execution)
{
    EXPECT_EXIT(ble_npl_callout_set_arg(nullptr, nullptr), ::testing::KilledBySignal(SIGABRT), "");
}

/*
 * Time functions
 */

TEST_F(TestNimbleNplOs, converting_ms_to_tick_with_a_null_output_pointer_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_time_ms_to_ticks(0, nullptr));
}

TEST_F(TestNimbleNplOs, converting_tick_to_ms_with_a_null_output_pointer_returns_an_error)
{
    EXPECT_EQ(BLE_NPL_INVALID_PARAM, ble_npl_time_ticks_to_ms(0, nullptr));
}

TEST_F(TestNimbleNplOs, waiting_with_cmsis_error_aborts_execution)
{
    osDelay_fake.return_val = osError;
    EXPECT_EXIT(ble_npl_time_delay(123), ::testing::KilledBySignal(SIGABRT), "");
}
